Skip to content

fix(gate-53): join src/registry.js — a manifest ref and its registration (#238) - #250

Merged
rubenvdlinde merged 1 commit into
mainfrom
fix/gate-53-registry-crossref
Aug 8, 2026
Merged

fix(gate-53): join src/registry.js — a manifest ref and its registration (#238)#250
rubenvdlinde merged 1 commit into
mainfrom
fix/gate-53-registry-crossref

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Closes #238.

Cause

gate-53 never read src/registry.js, so a manifest component ref and its registration were never cross-checked — and both directions were silent. That blind spot shipped ConductionNL/larpingapp#286: EventRoster was registered, resolvable, and named by no manifest position, so the event check-in surface had no entry point. It was unreachable UI long enough for its openspec task to be ticked over it, and the two gates that exist to catch manifest cross-reference defects — 22 and 53 — both reported PASS the whole time.

The old comment declined the registry wholesale: "the modal registry is app code (src/registry.js et al.) the gate cannot statically parse". It is app code, but it is not opaque — a fixed-shape ES module whose top-level export default { … } keys are its public surface. We cannot require() it (it pulls in .vue SFCs), but the keys extract with brace-depth tracking, which is exactly how the app-local test in larpingapp#288 already does it.

Directions and severities

dir shape severity why
2 manifest names a component no registry exports FAIL resolveTabComponent() falls through and the tab renders nothing. gate-14 route-reachability one layer up: unambiguously broken.
1 registry exports a section/page/widget no manifest names WARN An orphan is either wired or deleted and the gate cannot know which — the same "zero callers has two opposite fixes" property that governs gate-57. Report, don't prescribe.

Acceptance — larpingapp#286, both directions, against the real repo

state result
origin/development as shipped today (the #286 fix is in place) EventRoster not flagged
Check-in tab's component removed — #286 exactly as it shipped WARN, naming EventRoster
Check-in tab renamed to ThisComponentDoesNotExistAnywhere — issue #238's own control ERROR at /pages/17/config/sidebar/tabs/0/component, exit 1

False-positive controls — each pinned by a test a mutation breaks

Widening this check would fail every well-formed manifest in the fleet, so the exemptions are tested, not assumed:

  • Cn* names resolve from nextcloud-vue, not the app registry — exempt.
  • kind: 'modal' entries are not required to have a manifest position (open-modal is runtime-resolved; gate (b) already WARNs).
  • Metadata-only entries with no kind are not renderable surfaces.
  • A commented-out registration does not count as a registration. (A commented-out prelude counting as a prelude was a real false-GREEN in gate-64.)
  • An app with no src/registry.js skips check (f) entirely.

Blast radius — measured, repos at origin/development

repo exit before exit after new registry-crossref errors
portaliq 0 0 0
doriath 0 0 0
openconnector 0 0 0
larpingapp 0 0 0
procest 1 1 0

No verdict changes anywhere. procest's exit 1 is the pre-existing removals-invariant check, not this one.

Two new WARNs, both genuine orphans: portaliq CustomExample (kind page) and larpingapp ObjectDetail (kind section), neither named by any manifest position.

Tests — 13 new assertions over 3 new fixtures

registry-wired / registry-orphan / registry-missing. The wired fixture is the control: a check that only ever fires is as useless as one that never does.

Mutation results:

mutation assertions that fail
direction 2 disabled 4
direction 1 disabled 2
comment-stripping disabled 2
Cn* exemption removed 9
direction 1 severity inverted to FAIL 4

bash hydra-gates/tests/run-helper-suites.sh → 27 passed, 0 failed, 2 pre-existing quarantines. The existing good/broken fixture assertions (including exactly 5 error findings — none missed, none extra) are untouched: neither ships a src/registry.js.

Not done here

open-modal action targets are still WARNed as runtime-resolved. Now that the registry parses, those could be resolved too — but modal targets are not consistently registry-keyed across the fleet, so that is a separate change with its own blast-radius measurement.

…ion (#238)

gate-53 never read src/registry.js, so a manifest component ref and its
registration were never cross-checked and BOTH directions were silent. That
blind spot shipped larpingapp#286: `EventRoster` was registered, resolvable,
and named by no manifest position, so the event check-in surface had no entry
point. It was unreachable UI long enough for its openspec task to be ticked
over it, and the two gates that exist to catch manifest cross-reference
defects — 22 and 53 — both reported PASS the whole time.

The old comment declined the registry as "app code, not statically checkable".
It is app code, but it is not opaque: a fixed-shape ES module whose top-level
`export default { … }` keys are its public surface. We cannot require() it
(it pulls in .vue SFCs), but the keys extract with brace-depth tracking —
which is how the app-local test in larpingapp#288 already does it.

DIRECTIONS AND SEVERITIES

  2 → FAIL. A manifest `component` naming no registry key renders NOTHING.
      CnObjectSidebar.resolveTabComponent() falls through and the tab comes up
      blank. That is gate-14 route-reachability one layer up: unambiguously
      broken, so it blocks.
  1 → WARN. A registered component no manifest position names is either a
      component that should be wired or one that should be deleted. The gate
      cannot know which, so it reports rather than prescribes — the same
      "zero callers has two opposite fixes" property that governs gate-57.

ACCEPTANCE — larpingapp#286, both directions, against the real repo

  as shipped today (the #286 fix is in place)  EventRoster NOT flagged
  Check-in tab's `component` removed           WARN, names EventRoster
  Check-in tab renamed to a component nobody
    registers                                  ERROR at
    /pages/17/config/sidebar/tabs/0/component, exit 1

FALSE-POSITIVE CONTROLS, each pinned by a test that a mutation breaks:
Cn* names resolve from nextcloud-vue and are exempt; kind:'modal' entries are
not required to have a manifest position (open-modal is runtime-resolved and
gate (b) already warns); metadata-only entries with no kind are not surfaces;
a COMMENTED-OUT registration does not count as a registration; and an app with
no src/registry.js skips check (f) entirely.

BLAST RADIUS — measured, repos at origin/development, before vs after

  portaliq 0→0   doriath 0→0   openconnector 0→0   larpingapp 0→0
  procest  1→1  (its exit 1 is the pre-existing removals-invariant check,
                 not this one — zero registry-crossref errors)

No verdict changes anywhere. Two new WARNs, both genuine orphans:
portaliq `CustomExample` (kind page) and larpingapp `ObjectDetail`
(kind section), neither named by any manifest position.

13 new assertions over 3 new fixtures. Mutation-checked: disabling direction 2
fails 4, disabling direction 1 fails 2, dropping comment-stripping fails 2,
removing the Cn* exemption fails 9, and inverting direction 1 to a FAIL fails 4.
@rubenvdlinde
rubenvdlinde merged commit d910cb4 into main Aug 8, 2026
29 checks passed
@rubenvdlinde
rubenvdlinde deleted the fix/gate-53-registry-crossref branch August 8, 2026 13:45
rubenvdlinde pushed a commit that referenced this pull request Aug 8, 2026
Both hydra-gates-only; no overlap with this branch's five files.
rubenvdlinde added a commit that referenced this pull request Aug 8, 2026
…follow-up) (#260)

I measured the blast radius of #250 on five repos, found no new blocking
findings, and shipped. Five repos was not the fleet. A full sweep of all 20
afterwards found 38 new ERROR findings across five OTHER repos, and 34 of them
were FALSE.

Two causes, both a registration the parser could not see.

1. QUOTED, HYPHENATED KEYS — 25 false FAILs on hermiq.
   The matcher shared one character class between quoted and bare keys:

     /(?:^|[,{\s])(?:['"]?)([A-Za-z_$][\w$]*)(?:['"]?)\s*:/g

   `[\w$]*` excludes `-`, so `'agent-form'` captured `agent` and stopped at the
   hyphen. Every hyphenated registration was invisible, and every manifest
   reference to one — `agent-skills`, `agent-run-history`,
   `agent-tool-governance` — was reported as naming a component nobody
   registers. Quoted and bare keys are now separate alternatives with
   different classes.

2. src/customComponents.js — 9 false FAILs on softwarecatalog, 1 on hermiq.
   It is the SECOND registration source, and the tell was in the finding
   message I wrote myself: the runtime error it quotes says "not found in
   registry OR customComponents". Every app's own customComponents.js
   documents the resolution order in its header. I quoted the fallback and
   did not implement it. A component resolvable by EITHER route now resolves.

MEASURED, all 20 repos at origin/development, before vs after this commit:

  hermiq           25 ERROR -> 0     softwarecatalog   9 ERROR -> 0
  decidesk          1 ERROR -> 1     scholiq           1 ERROR -> 1
  shillinq          2 ERROR -> 2     all others        0 ERROR -> 0

The 4 survivors are true positives, each verified by hand against the app's
own wiring — App.vue passes only `:registry` in decidesk and scholiq, so a
component absent from it renders nothing:

  decidesk  MotionIntegrations  — MotionIntegrations.vue exists, registered
                                  nowhere, named by pages[].component
  scholiq   OrderPaymentPanel   — same shape
  shillinq  ImportWizard        — NO .vue file anywhere in src/, named by a
                                  manifest.d fragment
  shillinq  StockLedgerTrace    — same

New fixture registry-dialects covering both dialects plus the control that
keeps the exemption from becoming an amnesty: NotAnywherePanel is in neither
file and still FAILS. 5 new assertions. Mutations, each asserting it applied
before writing: reverting the regex fails 3, removing the customComponents
source fails 2, disabling direction 2 fails 6.

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

gate-53 effective-manifest-crossref never joins src/registry.js — a manifest component ref and an orphaned registry entry are BOTH silent

1 participant